package com.franklin.java.service.database.iu;

import com.alibaba.druid.DbType;
import com.flychord.jdk.util.matchcase.EntryCase;
import com.flychord.jdk.util.tuple.Tuple2;
import com.franklin.java.configuration.sql.run.JdbcConsoleState;
import com.franklin.java.configuration.sql.type.TypeMappingState;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.entity.sql.JdbcConsoleType;
import com.franklin.java.entity.sql.MappingType;
import com.franklin.java.service.database.*;
import com.franklin.java.utils.SqlHelper;
import com.franklin.plugins.common.collection.PrettyArrayList;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.PatternUtils;
import com.intellij.database.Dbms;
import com.intellij.database.model.*;
import com.intellij.database.psi.*;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.JBIterable;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * 基于IU，database tool开发的完整版实现
 *
 * @author Franklin
 * @since 2021/9/3 11:39
 */
public class DataSourceServiceForIU implements DataSourceService {

    public static final String JDBC_URL_PATTERN
        = "^jdbc:(?<databaseType>.*)://(?<ip>.*):(?<port>\\d*)/(?<database>.*)(\\?.*)?$";

    @Override
    public DatabaseHandler getDatabaseHandler(PsiElement psiElement) {
        Project project = psiElement.getProject();
        DbPsiFacade dbPsiFacade = DbPsiFacade.getInstance(project);
        List<DbDataSource> dataSources = dbPsiFacade.getDataSources();
        if (DataCheckUtils.isEmpty(dataSources)) {
            return new AbstractDatabaseHandler.EntityDatabaseHandler(psiElement);
        }
        List<DbNamespaceImpl> dbNamespaceList = new PrettyArrayList<>();
        for (DbDataSource dataSource : dataSources) {
            RawConnectionConfig connectionConfig = dataSource.getConnectionConfig();
            String targetDatabaseName = null;
            if (Objects.nonNull(connectionConfig)) {
                String effectiveUrl = connectionConfig.getEffectiveUrl(project);
                targetDatabaseName = PatternUtils.find(JDBC_URL_PATTERN, effectiveUrl, "databaseType", "ip", "port", "database")
                    .getOrDefault("database", "");
            }
            DasObject rootObject = dataSource.getDasObject();
            JBIterable<? extends DasObject> databasesIterable = rootObject.getDasChildren(ObjectKind.SCHEMA);
            Iterator<? extends DasObject> databasesItr = databasesIterable.iterator();
            while (databasesItr.hasNext()) {
                DasObject database = databasesItr.next();
                String databaseName = database.getName();
                if (database instanceof DbNamespaceImpl) {
                    DbNamespaceImpl dbNamespace = (DbNamespaceImpl) database;
                    if (DataCheckUtils.isNotEmpty(targetDatabaseName)) {
                        if (databaseName.equals(targetDatabaseName)) {
                            dbNamespaceList.add(dbNamespace);
                        }
                    } else {
                        dbNamespaceList.add(dbNamespace);
                    }
                }
            }
        }
        if (DataCheckUtils.isNotEmpty(dbNamespaceList)) {
            return new RemoteAndLocalDatabaseHandler(psiElement, dbNamespaceList);
        }
        return AbstractDatabaseHandler.EmptyDatabaseHandler.getInstance();
    }

    @Override
    public MyJdbcConsole getJdbcConsole(Project project, String sql) {
        String console = JdbcConsoleState.getConsole(project);
        JdbcConsoleType jdbcConsoleType = JdbcConsoleType.of(console);
        switch (jdbcConsoleType) {
            case DATABASE_TOOL:return new DatabaseToolJdbcConsole(project,sql);
            case FRANKLIN_JDBC_CONSOLE:return new FranklinJdbcConsole(project, sql);
        }
        return new DatabaseToolJdbcConsole(project,sql);
    }


    /**
     * 默认处理器
     *
     * @author Franklin
     * @since 2021/9/3 11:48
     */
    private static class DefaultDatabaseHandler extends AbstractDatabaseHandler {
        private DbNamespaceImpl dbNamespace;

        public DefaultDatabaseHandler(PsiElement psiElement, @NotNull DbNamespaceImpl dbNamespace) {
            super(psiElement);
            this.dbNamespace = dbNamespace;
        }

        @Override
        public DbType getDbType() {
            Dbms dbms = dbNamespace.getDbms();
            String displayName = dbms.getDisplayName();
            DbType dbType = DbType.of(displayName.toLowerCase());
            return Objects.isNull(dbType) ? DbType.mysql : dbType;
        }

        @Override
        public Map<String, List<ColumnInfo>> getColumns(Collection<String> tableNames) {
            Map<String, DbTable> tableNameTableMap = resolveTables(tableNames);
            MappingType mappingType = TypeMappingState.getMappingType(myProject);
            return tableNameTableMap.entrySet()
                .stream()
                .map(
                    EntryCase.match(
                        (tableName, dbTable) -> {
                            Map<String, DbColumn> columnNameColumnMap = resolveColumns(dbTable);
                            List<ColumnInfo> columnInfoList = columnNameColumnMap.values()
                                .stream()
                                .map(dbColumn -> {
                                    String type = "varchar(32)";
                                    String comment = "";
                                    //字段类型
                                    DataType dataType = dbColumn.getDataType();
                                    type = dataType.typeName;
                                    if (dataType.getLength() > 0) {
                                        type = type + "(" + dataType.getLength() + ")";
                                    }
                                    //字段描述
                                    String columnComment = dbColumn.getComment();
                                    comment = StringUtils.defaultString(columnComment, comment);
                                    if (DataCheckUtils.isNotEmpty(comment)) {
                                        comment = "db:" + comment;
                                    }
                                    String javaType = SqlHelper.resolveJavaType(type, mappingType);
                                    String columnName = dbColumn.getName();
                                    ColumnInfo columnInfo = new ColumnInfo(tableName, columnName, columnName);
                                    columnInfo.setJavaType(javaType);
                                    columnInfo.setComment(comment);
                                    columnInfo.setColumnType(type);

                                    String tableComment = dbTable.getComment();
                                    if (DataCheckUtils.isNotEmpty(tableComment)){
                                        columnInfo.setTableComment(tableComment);
                                    }
                                    return columnInfo;
                                })
                                .collect(Collectors.toList());
                            return tuple(tableName,columnInfoList);
                        }
                    )
                )
                .collect(Collectors.toMap(Tuple2::_1,Tuple2::_2));
        }

        @Override
        public void resolveColumnInfo(List<ColumnInfo> columnInfoList) {
            Map<String, List<ColumnInfo>> tableNameColumnsMap = columnInfoList.stream()
                .collect(Collectors.groupingBy(ColumnInfo::getTableName));

            doResolveColumnInfo(tableNameColumnsMap);
        }

        private Set<String> doResolveColumnInfo(Map<String, List<ColumnInfo>> tableNameColumnsMap) {
            if (DataCheckUtils.isEmpty(tableNameColumnsMap)) {
                return Collections.emptySet();
            }
            Set<String> tableNames = tableNameColumnsMap.keySet();
            Map<String, DbTable> tableNameTableMap = resolveTables(tableNames);

            if (DataCheckUtils.isEmpty(tableNameTableMap)) {
                return Collections.emptySet();
            }

            tableNameColumnsMap.forEach((tableName, columnInfos) -> {
                DbTable dbTable = tableNameTableMap.get(tableName);
                if (Objects.isNull(dbTable)) {
                    return;
                }
                Map<String, DbColumn> columnNameColumnMap = resolveColumns(dbTable);
                columnInfos.forEach(columnInfo -> {
                    DbColumn dbColumn = columnNameColumnMap.get(columnInfo.getColumnName());
                    String type = "varchar(32)";
                    String comment = "";
                    if (Objects.nonNull(dbColumn)) {
                        //字段类型
                        DataType dataType = dbColumn.getDataType();
                        type = dataType.typeName;
                        if (dataType.getLength() > 0) {
                            type = type + "(" + dataType.getLength() + ")";
                        }

                        //字段描述
                        String columnComment = dbColumn.getComment();
                        comment = StringUtils.defaultString(columnComment, comment);
                        if (DataCheckUtils.isNotEmpty(comment)) {
                            comment = "db:" + comment;
                        }

                    }
                    String tableComment = StringUtils.defaultString(dbTable.getComment(),"");
                    columnInfo.setTableComment(tableComment);
                    columnInfo.setColumnType(type);
                    columnInfo.setComment(comment);
                });
            });

            return tableNameTableMap.keySet();
        }

        /**
         * 解析数据源中的目标表<br>
         *
         * @param tableNames:
         * @return java.util.Map<java.lang.String, com.intellij.database.psi.DbTable>
         * @author Franklin
         * @since 2021/8/31 14:45
         */
        private Map<String, DbTable> resolveTables(Collection<String> tableNames) {
            JBIterable<DbElement> tableIteration = dbNamespace.getDasChildren(ObjectKind.TABLE);

            Map<String, DbTable> tableNameTableMap = new HashMap<>();
            Iterator<DbElement> itr = tableIteration.iterator();
            int size = tableNames.size();
            while (itr.hasNext()) {
                DbElement dbElement = itr.next();
                if (dbElement instanceof DbTable) {
                    DbTable dbTable = (DbTable) dbElement;
                    if (tableNames.contains(dbTable.getName())) {
                        tableNameTableMap.put(dbTable.getName(), dbTable);
                        if (tableNameTableMap.size() == size) {
                            break;
                        }
                    }
                }
            }

            return tableNameTableMap;
        }

        private Map<String, DbColumn> resolveColumns(DbTable dbTable) {
            JBIterable<? extends DbElement> columns = dbTable.getDasChildren(ObjectKind.COLUMN);
            JBIterable<? extends DbElement> keys = dbTable.getDasChildren(ObjectKind.KEY);
            return columns
                .filter(column -> column instanceof DbColumn)
                .map(column -> (DbColumn) column)
                .toMap(DasNamed::getName, col -> col);
        }
    }

    /**
     * 多数据库解析器
     */
    private static class MultipleDatabaseHandler extends AbstractDatabaseHandler {

        private List<DefaultDatabaseHandler> handlerList;

        protected MultipleDatabaseHandler(PsiElement psiElement, List<DbNamespaceImpl> dbNamespaceList) {
            super(psiElement);
            this.handlerList = dbNamespaceList.stream()
                .map(db -> new DefaultDatabaseHandler(psiElement, db))
                .collect(Collectors.toList());
        }

        @Override
        public void resolveColumnInfo(List<ColumnInfo> columnInfoList) {
            Map<String, List<ColumnInfo>> tableNameColumnsMap = columnInfoList.stream()
                .collect(Collectors.groupingBy(ColumnInfo::getTableName));

            for (DefaultDatabaseHandler handler : handlerList) {
                //已处理的表名
                Set<String> handledTableNames = handler.doResolveColumnInfo(tableNameColumnsMap);
                //处理过的不再处理，同表名只处理一次
                handledTableNames.forEach(tableNameColumnsMap::remove);
            }
        }

        @Override
        public Map<String, List<ColumnInfo>> getColumns(Collection<String> tableNames) {
            Collection<String> tempTableNames = tableNames;
            Map<String,List<ColumnInfo>> result = new HashMap<>();
            for (DefaultDatabaseHandler handler : handlerList) {
                if (DataCheckUtils.isNotEmpty(tempTableNames)) {
                    Map<String, List<ColumnInfo>> columns = handler.getColumns(tempTableNames);
                    if (DataCheckUtils.isNotEmpty(columns)) {
                        result.putAll(columns);
                        Set<String> handleTables = columns.keySet();
                        tempTableNames = tempTableNames
                            .stream()
                            .filter(tableName -> !handleTables.contains(tableName))
                            .collect(Collectors.toSet());
                    }
                }else {
                    break;
                }
            }
            return result;
        }
    }

    /**
     * 远程数据库 + 本地实体类 数据库处理器
     */
    private static class RemoteAndLocalDatabaseHandler extends MultipleDatabaseHandler {

        protected RemoteAndLocalDatabaseHandler(PsiElement psiElement, List<DbNamespaceImpl> dbNamespaceList) {
            super(psiElement, dbNamespaceList);
        }

        @Override
        public void resolveColumnInfo(List<ColumnInfo> columnInfoList) {
            super.resolveColumnInfo(columnInfoList);

            List<ColumnInfo> unHandleColumnList = columnInfoList.stream()
                .filter(columnInfo -> DataCheckUtils.isEmpty(columnInfo.getComment()))
                .collect(Collectors.toList());

            if (DataCheckUtils.isNotEmpty(unHandleColumnList)) {
                EntityDatabaseHandler entityDatabaseHandler = new EntityDatabaseHandler(psiElement);
                entityDatabaseHandler.resolveColumnInfo(unHandleColumnList);
            }
        }

        @Override
        public Map<String, List<ColumnInfo>> getColumns(Collection<String> tableNames) {
            Map<String, List<ColumnInfo>> dbColumns = super.getColumns(tableNames);
            if (dbColumns.size() == tableNames.size()){
                return dbColumns;
            }
            Map<String, List<ColumnInfo>> result = new HashMap<>();
            EntityDatabaseHandler entityDatabaseHandler = new EntityDatabaseHandler(psiElement);
            tableNames.forEach(tableName -> {
                List<ColumnInfo> dbColList = dbColumns.get(tableName);
                if (DataCheckUtils.isNotEmpty(dbColList)){
                    result.put(tableName,dbColList);
                }else {
                    Map<String, List<ColumnInfo>> entityCols = entityDatabaseHandler.getColumns(Collections.singleton(tableName));
                    result.putAll(entityCols);
                }
            });
            return result;
        }
    }
}
